perf(memtrack): reduce serialization bottleneck#436
Conversation
Merging this PR will not alter performance
|
381bac8 to
9be9b22
Compare
Greptile SummaryThis PR replaces the single-threaded, event-at-a-time memtrack write path with a parallel pipeline: events are batched into 64K-event windows, each window is divided into fixed-size zstd frames that are compressed concurrently by a Rayon pool, and the frames are written to disk in input order. It also switches the BPF ring buffer poller from
Confidence Score: 5/5The PR is safe to merge. The new pipeline correctly preserves event order, bounds peak memory, and handles shutdown cleanly. The core encoding, ordering, and shutdown paths were all traced end-to-end and are correct. Tests cover byte-identity of serialisation, order preservation across parallel frames, window boundaries, and the empty-stream edge case. The only findings are a stale comment on compression level and thread-pool creation inside the benchmark loop. No files require special attention; the benchmark file has a minor measurement-noise issue worth a second glance. Important Files Changed
|
a225638 to
9561cdd
Compare
9561cdd to
9292b3f
Compare
346a162 to
99b9b68
Compare
…t zstd compression bottleneck
Callers that encode a frame into an in-memory buffer need the buffer back after the zstd stream is finalized, so frames can be composed into a larger artifact stream. Refs COD-3071
Group events into fixed-size frames and compress them across a rayon pool, writing windows in input order. Bounds peak memory regardless of run length and removes the single-threaded compression bottleneck. Refs COD-3071
…race Generate a seeded malloc/free/realloc/mmap workload with a live-heap model instead of uniform random events, and sweep worker counts. Refs COD-3071
Forward ring buffer events over a crossbeam channel directly instead of through an extra keep-alive forwarding thread. On shutdown, consume the ring buffer once more so events emitted after the last poll are not lost, then close the channel. Refs COD-3071
Feed the poller's event channel straight into encode_events on one thread, sized to available parallelism. Shutdown is now deterministic: stop the poller, which drains stragglers and closes the channel, then join the pipeline. Fixes COD-3071
itertools::chunks erases the iterator size hint, so collecting each window grew by doubling (~29% of encode driver time in reallocation). A single pre-sized buffer reused across windows removes the growth and the chunk-adapter bookkeeping: encode_events_realistic median drops 21%/17%/9% at 16/8/4 workers.
99b9b68 to
78d5ae2
Compare
Submitting with default flags wakes the consumer on nearly every event, and the wakeup dominates submission cost at high event rates. Submit with BPF_RB_NO_WAKEUP until 64KB of unconsumed data has accumulated, then force a wakeup. Kernel ringbuf benchmarks show up to ~20x higher sustained throughput with sampled notification. The poller's 10ms poll timeout flushes the tail that never reaches the watermark. Refs COD-3071
16 MiB holds only ~300K records (~60ms of headroom at multi-M events/s bursts), so a short consumer scheduling stall overflows the buffer and drops events. 256 MiB gives ~1s of worst-case burst headroom. The buffer is memcg-charged and vmap'd, so the only cost is the memory itself while tracking runs. Refs COD-3071
Two consumer-side fixes for event drops under allocation bursts: - After each poll() the poller calls consume() once, which drains the ring buffer to empty without paying an epoll wakeup round-trip per poll cycle. - The encode pipeline no longer claims every core: rayon workers on all cores starve the poll thread (and the tracked command), which is what let the ring buffer fill up. Reserve two cores. Refs COD-3071
64cd996 to
5c27231
Compare
Summary
MemtrackEventserialization with a byte-identical manual serializerMemtrackWriter::finishreturn encoded frame bytes for batchingVerification
cargo test -p runner-shared artifacts::memtrackcargo test -p runner-sharedcargo check -p runner-sharedcargo test --manifest-path crates/memtrack/Cargo.tomlvia Nix shell with libclang/build deps; eBPF integration cases compiled but were ignored becauseGITHUB_ACTIONSwas unsetcargo bench -p runner-shared --bench memtrack_writerNotes
memtrack_writerbench measures the Phase A single-writer encoder path only, not the full parallel memtrack pipeline.